# -*- coding: utf-8 -*-
"""\
This file is part of the ptycho package.

Utility functions and classes to deal with input parameters.

"""
from pyE17 import io
import os

__all__ = ['Param', 'load', 'asParam']


class Param(object):
    """Parameter container
    
    In normal use case, this class is not meant to be instantiated by the user.
    See FSParamFactory and NetworkParamFactory.
    
    By default the class has "autovivification" enabled, meaning that access to any
    unknown attribute results in its automatic creation (an instance of Param). As a result,
    this code is legal:
        
    >>> p = Param()
    >>> p.x = 1
    >>> p.y.a = 1  # This creates p.y as a Param object
    
    To turn this off, one only needs to do
    >>> p.autoviv = False
    This will be propagated to all Param children as well.
    """

    def __init__(self, old_param=None, autoviv=True, none_if_absent=False):
        """Parameter structure initialization
        
        (old_param can be a Param object or a nested dict).
        """
        self._autoviv = autoviv
        self._none_if_absent = none_if_absent
        if old_param is None: 
            return
        elif isinstance(old_param, dict):
            self._from_dict(old_param)
        elif isinstance(old_param, type(self)):
            self._from_dict(old_param._to_dict())

    def __getattr__(self, item):
        if self._autoviv:
            if '(' in item: return
            value = type(self)(autoviv=True)
            setattr(self, item, value)
            return value
        elif self._none_if_absent:
            setattr(self, item, None)
            return None
        else:
            raise AttributeError
            
    def _to_dict(self):
        return dict( (k,v._to_dict()) if isinstance(v, type(self)) else (k,v) for k,v in self.__dict__.iteritems() if not k.startswith('_'))

    def _from_dict(self, d):
        for k,v in d.iteritems():
            if isinstance(v, dict):
                setattr(self, k, type(self)(v))
            else:
                setattr(self, k, v)

    def _copy(self):
        return Param(self._to_dict())
        
    # These are properties to make sure that an error is raised if an attempt is made
    # at overwriting them.
    @property
    def keys(self):
        """Access the key method"""
        return self.__dict__.keys

    @property
    def iteritems(self):
        """Access the key method"""
        return self.__dict__.iteritems
        
    @property
    def copy(self):
        "Deep copy"
        return self._copy

    @property
    def none_if_absent(self):
        "Switch for returning None if absent"
        return self._none_if_absent
        
    @none_if_absent.setter
    def none_if_absent(self, value):
        # propagate to children
        self._none_if_absent = value
        self._autoviv = False
        for k,v in self.__dict__.iteritems():
            if isinstance(v, type(self)) and k[0] != '_':
                v.none_if_absent = value
                v._autoviv = False

    @property
    def autoviv(self):
        "Autovivication switch"
        return self._autoviv

    @autoviv.setter
    def autoviv(self, value):
        # propagate to children
        self._autoviv = value
        self._none_if_absent = False
        for k,v in self.__dict__.iteritems():
            if isinstance(v, type(self)) and k[0] != '_':
                v.autoviv = value
                v._none_if_absent = False
        
    def __repr__(self):
        return 'Param(%s)' % repr(self._to_dict())      

def asParam(obj, autoviv=False, none_if_absent=False):
    """
    Returns the object if it is already a Param, otherwise
    (i.e. obj is a dict) attempts to build a Param from it.
    This sets autoviv to False by default.
    """
    if type(obj) is Param:
        out = obj
        out.autoviv = autoviv
        out.none_if_absent = none_if_absent
    else:
        out = Param(obj, autoviv=autoviv, none_if_absent=none_if_absent)
    return out  

def load(filename):
    """\
    Helper function to load a parameter file
    """
    filename = os.path.abspath(os.path.expanduser(filename)) 
    param_dict = io.h5read(filename)
    param = Param(param_dict)
    param.autoviv = False
    return param
